Skip to content

fix: remove unsound structured-output Tapioca compiler - #405

Merged
jbeckwith-oai merged 1 commit into
mainfrom
codex/remove-unsound-base-model-tapioca
Aug 13, 2026
Merged

fix: remove unsound structured-output Tapioca compiler#405
jbeckwith-oai merged 1 commit into
mainfrom
codex/remove-unsound-base-model-tapioca

Conversation

@jbeckwith-oai

Copy link
Copy Markdown
Contributor

Summary

  • Remove the unreleased OpenAI::BaseModel Tapioca DSL compiler, its compiler-specific README section, and the compiler-only tests.
  • Preserve the independently useful Rails/Tapioca load-order handling and nullable-array Sorbet conversion fixes from feat: add Tapioca typing for structured outputs #364.
  • Add focused compatibility regressions proving application-defined structured-output models preserve raw nested hashes and arrays on construction/assignment, while Chat Completions and Responses both hydrate nested models in parsed API responses.
  • Keep the nullable-array Sorbet regression in an isolated subprocess so sorbet-runtime remains optional for normal SDK usage.

Why this is the correct architectural rollback

#364 addressed the customer report in #309 by adding a compiler that generated readers such as Participant and T::Array[Participant]. Those signatures are unsound for the SDK's supported transport-model contract:

event = Event.new(participant: {name: "Ada"}, participants: [{name: "Grace"}])
event.participant       # => Hash
event.participants.first # => Hash

Parsed API responses correctly hydrate those same fields into application-defined model objects. The original compiler test only type-checked synthetic RBI usage; it never compared generated reader signatures with direct model construction, assignment, or both public parsed-response boundaries. The proposed runtime workaround in #385 introduced additional coercion and conflicting accessor/storage semantics instead of addressing the incorrect abstraction.

Architecture review concluded that SDK transport models should remain permissive transport models. Sound application-owned typed structured outputs should instead be designed at the structured-output integration boundary. #309 has therefore been reopened and rewritten around the original customer outcome rather than the rejected implementation.

The removed compiler has not appeared in a released gem, so removing it now avoids publishing an unsound API contract. The actual live Castiron generator also emitted this compiler; its coordinated upstream removal is https://github.com/openai/openai/pull/1291001.

Why retain the other #364 changes

  • lib/openai.rb loader detection independently fixes Rails/Tapioca load order and retains its existing regression coverage.
  • ArrayOf#to_sorbet_type correctly preserves nullable array elements independently of the deleted compiler; the new isolated subprocess test preserves coverage without making Sorbet a runtime dependency.
  • Existing SDK request-model raw-value contract tests remain unchanged, and new public OpenAI::BaseModel tests cover both individual nested fields and nested arrays.

Validation

  • Ruby 4.0.6: 695 tests, 3,134 assertions, zero failures/errors/skips.
  • Ruby 3.4.10: 695 tests, 3,134 assertions, zero failures/errors/skips.
  • Ruby 3.3.12: 695 tests, 3,134 assertions, zero failures/errors/skips.
  • bundle exec rake lint: 2,618 RuboCop files clean, Sorbet clean, 1,212 RBS files valid.
  • bundle exec rake build:gem; inspected the built gem to verify it contains no Tapioca compiler and does not add sorbet-runtime as a runtime dependency.
  • Castiron companion: 574 Ruby renderer tests passed, including assertions that both generation profiles omit the compiler while preserving their intended loader behavior.

Related customer issue: #309. Do not auto-close it: the underlying typed structured-output design remains open.

@jbeckwith-oai
jbeckwith-oai requested a review from a team as a code owner August 13, 2026 20:38
@openai-sdks

openai-sdks Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

OkTest Summary

🟡 Running for Ruby SDK PR #405.

View OkTest run #31741832850

SDK merge (pending) · head (a75eb55520a3) · base (cbda8bc0e6e3) · OkTest (a845fa206fa4)

@jbeckwith-oai jbeckwith-oai added the generator Touches generated SDK files label Aug 13, 2026

@HAYDEN-OAI HAYDEN-OAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the compiler rollback, transport-model compatibility, structured-output hydration, and optional Sorbet boundary. Looks good.

@jbeckwith-oai
jbeckwith-oai added this pull request to the merge queue Aug 13, 2026

@HAYDEN-OAI HAYDEN-OAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved after reviewing all six changed files and the coordinated Castiron generator removal. The unreleased compiler can be removed without breaking published APIs; direct model construction and assignment still preserve caller-owned raw values, while both Chat Completions and Responses hydrate nested structured outputs. Nullable-array Sorbet types, optional sorbet-runtime, and Rails/Tapioca loader behavior remain intact.

Merged via the queue into main with commit 826769a Aug 13, 2026
13 checks passed
@jbeckwith-oai
jbeckwith-oai deleted the codex/remove-unsound-base-model-tapioca branch August 13, 2026 21:21
@openai-sdks openai-sdks Bot mentioned this pull request Aug 13, 2026
virelai2604 pushed a commit to virelai2604/openai-ruby that referenced this pull request Aug 15, 2026
Automated Release PR
---


##
[0.79.0](openai/openai-ruby@v0.78.0...v0.79.0)
(2026-08-14)


### Features

* add HTTP response observability
([openai#365](openai#365))
([48d36b7](openai@48d36b7))
* add Tapioca typing for structured outputs
([openai#364](openai#364))
([738e2b4](openai@738e2b4))
* **api:** Add new model identifiers and remove audit log source
([e0a4bc5](openai@e0a4bc5))
* **api:** add WebSocket stream IDs
([openai#387](openai#387))
([5880287](openai@5880287))
* **api:** add workload identity access token issued event
([openai#372](openai#372))
([9199779](openai@9199779))
* **api:** deprecate Sora video APIs
([openai#386](openai#386))
([0664456](openai@0664456))
* **client:** add default headers
([openai#369](openai#369))
([d516874](openai@d516874))
* expose request IDs
([openai#352](openai#352))
([c15fb7b](openai@c15fb7b))
* support Azure OpenAI v1
([openai#355](openai#355))
([c1d223e](openai@c1d223e))


### Bug Fixes

* **api:** Add new model identifiers and remove audit log source
([openai#360](openai#360))
([e0a4bc5](openai@e0a4bc5))
* **api:** allow nil timeout in client signatures
([openai#363](openai#363))
([e52ca61](openai@e52ca61))
* **api:** document file upload metadata defaults
([openai#361](openai#361))
([b4bc1ea](openai@b4bc1ea))
* coerce nested BaseModel fields
([openai#295](openai#295))
([e5152a9](openai@e5152a9))
* enable streaming when retrieving responses
([openai#413](openai#413))
([fd85182](openai@fd85182))
* encode multipart array and nested fields
([openai#348](openai#348))
([1c71a1e](openai@1c71a1e))
* honor workload identity environment defaults
([openai#398](openai#398))
([f4afea1](openai@f4afea1))
* keep required path parameters out of resource query strings
([openai#402](openai#402))
([6768a7c](openai@6768a7c))
* loading after ActiveSupport 6 subclass extensions
([openai#346](openai#346))
([20fbb09](openai@20fbb09))
* make SDK debug body logging fail closed
([openai#411](openai#411))
([c4f7284](openai@c4f7284))
* package every README-linked guide and example
([openai#382](openai#382))
([ddecd0f](openai@ddecd0f))
* preserve binary multipart stream reads
([openai#354](openai#354))
([b3a1ba7](openai@b3a1ba7))
* preserve existing BaseModel instances during coercion
([openai#400](openai#400))
([fbd8560](openai@fbd8560))
* preserve prefixed idempotency headers on redirects
([openai#404](openai#404))
([c380e13](openai@c380e13))
* preserve stream identity with HTTP logging
([openai#384](openai#384))
([640a1fe](openai@640a1fe))
* prevent symbol-keyed headers from bypassing security filters
([openai#383](openai#383))
([b8165af](openai@b8165af))
* redact sensitive query and form logging
([openai#389](openai#389))
([7aa54e4](openai@7aa54e4))
* reject invalid webhook signing secrets
([openai#403](openai#403))
([ad12458](openai@ad12458))
* remove unsound structured-output Tapioca compiler
([openai#405](openai#405))
([826769a](openai@826769a))
* restore RuboCop coverage for RBI files
([openai#388](openai#388))
([d2d4b5d](openai@d2d4b5d))
* return values from interruptible enumerator
([openai#350](openai#350))
([327dad7](openai@327dad7))
* strip credential headers on cross-origin redirects
([openai#391](openai#391))
([f50f08c](openai@f50f08c))
* tighten JSON, JSONL, and SSE content type matching
([openai#277](openai#277))
([66359a0](openai@66359a0))
* use API field names in structured output schemas
([openai#390](openai#390))
([d1e3cf3](openai@d1e3cf3))
* validate and bound retry delays
([openai#392](openai#392))
([fff94e7](openai@fff94e7))
* **webhooks:** support Rack and case-insensitive HTTP headers
([openai#401](openai#401))
([37de980](openai@37de980))


### Reverts

* nested BaseModel coercion
([openai#295](openai#295))
([openai#375](openai#375))
([65fa76e](openai@65fa76e))


### Chores

* Cover Ruby files at the SDK root
([openai#362](openai#362))
([d11f25b](openai@d11f25b))
* enforce boolean symbol lint
([openai#395](openai#395))
([fafdf2e](openai@fafdf2e))
* enforce deprecated constant lint
([openai#379](openai#379))
([0def912](openai@0def912))
* enforce duplicate match pattern lint
([openai#380](openai#380))
([7a8f2fb](openai@7a8f2fb))
* enforce empty else lint
([openai#396](openai#396))
([1e073c1](openai@1e073c1))
* enforce line length lint
([openai#406](openai#406))
([4592061](openai@4592061))
* enforce line length on base models
([openai#416](openai#416))
([aef1e33](openai@aef1e33))
* enforce line length on requires
([openai#415](openai#415))
([c24e045](openai@c24e045))
* enforce missing RuboCop enable directives
([openai#368](openai#368))
([50046bf](openai@50046bf))
* enforce missing super lint
([openai#393](openai#393))
([2a42bd0](openai@2a42bd0))
* enforce nonempty pattern branches
([openai#409](openai#409))
([9d22d5c](openai@9d22d5c))
* enforce redundant directive lint
([openai#366](openai#366))
([8448a4c](openai@8448a4c))
* enforce redundant exception lint
([openai#397](openai#397))
([cbda8bc](openai@cbda8bc))
* enforce symbol conversion lint
([openai#381](openai#381))
([0531498](openai@0531498))
* enforce useless assignment lint
([openai#394](openai#394))
([9688836](openai@9688836))
* guard RuboCop suppression directives
([openai#408](openai#408))
([5b6e0dc](openai@5b6e0dc))
* lint Ruby files repository-wide
([openai#377](openai#377))
([8ca8426](openai@8ca8426))
* remove Stainless attribution and infrastructure
([openai#371](openai#371))
([33d2c86](openai@33d2c86))
* require MFA for gem releases
([openai#378](openai#378))
([71798ec](openai@71798ec))


### Documentation

* **api:** describe response stream event unions
([openai#412](openai#412))
([87d18ef](openai@87d18ef))
* clarify file upload metadata
([openai#358](openai#358))
([698ac65](openai@698ac65)),
closes [openai#243](openai#243)
* document fiber scheduler concurrency
([openai#357](openai#357))
([25a7bbc](openai@25a7bbc))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: openai-sdks[bot] <284451331+openai-sdks[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

generator Touches generated SDK files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants